// blockchain
package main

import (
	"bytes"
	"crypto/ecdsa"
	"encoding/hex"
	"fmt"
	"github.com/boltdb/bolt"
	"log"
	"os"
)

const dbFile = "blockchain.db" //数据库文件名
const blockBucket = "blocks"   //名称
const genesisCoinbaseData = "飞哥带你来挖矿"

type BlockChain struct {
	tip []byte   //二进制数据
	db  *bolt.DB //数据库
	//blocks []*Block // 一个数组，每个元素都是指针，存储block区块
}


//挖矿带来的交易
func (blockchain *BlockChain)MineBlock(transactions []*Transaction){
	var lastHash []byte	//最后的哈希
	for _,tx := range transactions{
		if blockchain.VertifyTransaction(tx)!=true{
				log.Panic("交易不正确，有错误")
			}
		}
	err := blockchain.db.View(func(tx *bolt.Tx) error{
		bucket := tx.Bucket([]byte(blockBucket))	//查看数据
		lastHash=bucket.Get([]byte("1"))			//取出最后区块的哈希
		return nil
	})
	if err!=nil {
		log.Panic(err)	//处理错误
	}
	newBlock := NewBlock(transactions,lastHash)	//创建一个新的区块
	err = blockchain.db.Update(func(tx *bolt.Tx)error {
		bucket := tx.Bucket([]byte(blockBucket))
		err:=bucket.Put(newBlock.Hash,newBlock.Serialize())	//	存入数据库
		if err!=nil{
			log.Panic(err)	//处理错误
		}
		err = bucket.Put([]byte("1"),newBlock.Hash)
		if err!=nil{
			log.Panic(err)	//处理错误
		}
		blockchain.tip = newBlock.Hash	//保存上一块的哈希
		return nil
	})
}

//获取没使用输出的交易请求列表
//查找没有花销的交易=挖矿类
func (blockchain *BlockChain)FindUnspentTransactions(pubkeyhash []byte) []Transaction {
	var unspentTXs []Transaction		//交易事务
	spentTXOS := make(map[string][]int)	//开辟内存
	bci := blockchain.Iterator()	//迭代器
	for {
		block := bci.next()	//循环下一个
		for _,tx := range block.Transactions{//循环每个交易
			txID := hex.EncodeToString(tx.ID)		//获取交易编号
		Outputs:
			for outindex,out := range tx.Vout{		//循环遍历vout
				if spentTXOS[txID]!=nil{
					for _,spentOut:=range spentTXOS[txID]{
						if spentOut == outindex{
							continue Outputs		//循环到不等为止
						}
					}
				}
				if out.isLockedWithKey(pubkeyhash){
					unspentTXs = append(unspentTXs,*tx)		//加入列表
				}
			}
			if tx.IsCoinBase() == false{
				for _,in := range tx.Vin{
					if in.UsesKey(pubkeyhash){		//判断是否可以锁定
						inTxID:=hex.EncodeToString(in.Txid)	//编码为字符串
						spentTXOS[inTxID] = append(spentTXOS[inTxID],in.Vout)
					}
				}
			}
		}
		if len(block.PrevBlockHash) == 0{//最后一块，跳出
			break
		}
	}
	return unspentTXs
}

//获取所有没有使用的交易
func (blockchain *BlockChain)FindUTXO() map[string]TXoutputs{
	UTXO := make(map[string]TXoutputs)	//新建序列
	spentTXOs:=make(map[string][]int)	//花掉的交易
	bci := blockchain.Iterator()	//迭代器
	for {
		block := bci.next()
		for _,tx := range block.Transactions {
			txID := hex.EncodeToString(tx.ID) //根据编号编码
		Outputs:
			for outIdx, out := range tx.Vout {
				if spentTXOs[txID]!=nil {
					for _,spentoutidx := range spentTXOs[txID]{
						if spentoutidx==outIdx {
							continue Outputs
						}
					}
				}
				outs := UTXO[txID]
				outs.Outputs=append(outs.Outputs,out)	//叠加
				UTXO[txID ] = outs	//抓取编号复制保存
			}
			if tx.IsCoinBase() == false{
				for _,in := range tx.Vin{
					inTxID := hex.EncodeToString(in.Txid)	//编码
					spentTXOs[inTxID] = append(spentTXOs[inTxID],in.Vout)	//追加
				}
			}
		}
			if len(block.PrevBlockHash) == 0 {
				break
			}
	}

	return UTXO
}

//查找进行转账的交易
//获取所有没有使用的输出以参考输入
func (blockchain *BlockChain)FindSpendableOutputs(pubkeyhash []byte,amount int) (int,map[string][]int) {
	unspentOutputs := make(map[string][]int)	//输出
	unspentTxs := blockchain.FindUnspentTransactions(pubkeyhash)	//根据地址查询交易
	accmulated:=0
Work:
	for _,tx := range unspentTxs{
		txID:=hex.EncodeToString(tx.ID)		//获取编号
		for outindex,out := range tx.Vout{
			if out.isLockedWithKey(pubkeyhash)&&accmulated<amount{
				accmulated += out.Value	//统计金额
				unspentOutputs[txID] = append(unspentOutputs[txID],outindex)		//序列叠加
				if accmulated>=amount{
					break Work
				}
			}
		}
	}
	return accmulated,unspentOutputs
}

//判断数据库是否存在
func dbExists() bool {
	if _,err:= os.Stat (dbFile);os.IsNotExist(err){
		return false
	}
	return true
}

/*
// 增加一个区块
func (block *BlockChain) AddBlock(data string) {
	var lastHash []byte //上一块哈希
	err := block.db.View(func(tx *bolt.Tx) error {
		block := tx.Bucket([]byte(blockBucket)) //取得数据
		lastHash = block.Get([]byte("1"))       //取得第一块
		return nil
	})
	if err != nil {
		log.Panic(err) //处理数据库打开错误
	}
	newBlock := NewBlock(data, lastHash) //创建一个新的区块
	err = block.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))               //取出
		err := bucket.Put(newBlock.Hash, newBlock.Serialize()) //亚茹数据
		if err != nil {
			log.Panic(err) //处理压入错误
		}
		err = bucket.Put([]byte("1"), newBlock.Hash) //压入数据
		if err != nil {
			log.Panic(err) //处理压入错误
		}
		block.tip = newBlock.Hash //处理
		return nil
	})
}
*/

//迭代器
func (block *BlockChain) Iterator() *BlockChainIterator {
	bcit := &BlockChainIterator{block.tip, block.db}
	return bcit //根据区块链创建区块链迭代器
}

// 创建一个区块链
func NewBlockChain(address string) *BlockChain {
	if dbExists()==false{
		fmt.Println("数据库不存在，请先创建一个")
		os.Exit(1)
	}
	var tip []byte                          //存储区块链的二进制数据
	db, err := bolt.Open(dbFile, 0600, nil) //打开数据库
	if err != nil {
		log.Panic(err) //处理数据库打开错误
	}
	// 处理数据更新
	err = db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket)) //按照名称打开数据库的表格
		tip=bucket.Get([]byte("1"))
		//if bucket == nil {
		//	fmt.Println("当前数据库没有区块链，没有创建一个新的")
		//	genesis := NewGenesisBlock()                        //创建创世区块
		//	bucket, err := tx.CreateBucket([]byte(blockBucket)) //创建一个数据库
		//	if err != nil {
		//		log.Panic(err) //处理创建错误
		//	}
		//	err = bucket.Put(genesis.Hash, genesis.Serialize()) //存入数据
		//	if err != nil {
		//		log.Panic(err) //处理存入错误
		//	}
		//	err = bucket.Put([]byte("1"), genesis.Hash) //存入数据
		//	if err != nil {
		//		log.Panic(err) //处理存入错误
		//	}
		//	tip = genesis.Hash //取得哈希
		//} else {
		//	tip = bucket.Get([]byte("1"))
		//}
	 	return nil
	})
	if err != nil {
		log.Panic(err) //处理数据库更新错误
	}
	bc := BlockChain{tip, db} //创建一个区块链
	return &bc
}

//创建一个区块链创建一个数据库
func CreateBlockChain(address string)*BlockChain{
	if dbExists(){
		fmt.Println("数据库已经存在，无需创建")
		os.Exit(1)
	}
	var tip []byte                          //存储区块链的二进制数据
	cbtx:=NewCoinBaseTX(address, genesisCoinbaseData)	//创建创世区块的事务交易
	genesis := NewGenesisBlock(cbtx)	//创建创世区块的块

	db, err := bolt.Open(dbFile, 0600, nil) //打开数据库
	if err != nil {
		log.Panic(err) //处理数据库打开错误
	}
	err = db.Update(func(tx *bolt.Tx) error {

		bucket,err := tx.CreateBucket([]byte(blockBucket))
		if err!=nil {
			log.Panic(err)	//处理数据库打开错误
		}
		err=bucket.Put(genesis.Hash,genesis.Serialize())	//存储
		if err!=nil {
			log.Panic(err)	//处理数据库打开错误
		}
		err = bucket.Put([]byte("1"),genesis.Hash)
		if err!=nil {
			log.Panic(err)	//处理数据库打开错误
		}
		tip = genesis.Hash
		return nil
	})
	if err!=nil {
		log.Panic(err)	//处理数据库打开错误
	}
	bc := BlockChain{tip, db} //创建一个区块链
	return &bc
}
//交易签名
func (BlockChain *BlockChain)SignTransaction (tx *Transaction,privatekey ecdsa.PrivateKey) {
	prevTXs := make(map[string]Transaction)
	for _,vin := range tx.Vin{
		preTx,err := BlockChain.FindTransaction(vin.Txid)
		if err!=nil {
			log.Panic(err)
		}
		prevTXs[hex.EncodeToString(preTx.ID)] = preTx
	}
	tx.Sign(privatekey,prevTXs)
}

func (blockchain *BlockChain)FindTransaction(ID []byte)(Transaction,error){
	bci := blockchain.Iterator()
	for{
		block := bci.next()
		for _,tx:=range block.Transactions{
			if bytes.Compare(tx.ID,ID)==0 {
				return *tx,nil
			}
		}
		if len(block.PrevBlockHash)==0 {
			break
		}
	}
	return Transaction{},nil
}

func (blockchain *BlockChain)VertifyTransaction(tx *Transaction)bool  {
	prevTxs := make(map[string]Transaction)
	for _,vin := range tx.Vin{
		prevTx,err := blockchain.FindTransaction(vin.Txid)	//查找交易
		if err!=nil {
			log.Panic(err)
		}
		prevTxs[hex.EncodeToString(prevTx.ID)] = prevTx
	}
	return tx.Verify(prevTxs)
}